package com.hushui.mall.coupon.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hushui.common.to.mq.SeckillOrderTo;
import com.hushui.common.utils.PageUtils;
import com.hushui.common.utils.Query;
import com.hushui.common.utils.VoUtils;
import com.hushui.mall.coupon.dao.SeckillSessionDao;
import com.hushui.mall.coupon.entity.SeckillSessionEntity;
import com.hushui.mall.coupon.entity.SeckillSkuRelationEntity;
import com.hushui.mall.coupon.feign.ProductFeignService;
import com.hushui.mall.coupon.service.SeckillSessionService;
import com.hushui.mall.coupon.service.SeckillSkuRelationService;
import com.hushui.mall.coupon.to.SecKillSkuRedisTo;
import com.hushui.mall.coupon.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@Slf4j
@Service("seckillSessionService")
public class SeckillSessionServiceImpl extends ServiceImpl<SeckillSessionDao, SeckillSessionEntity> implements SeckillSessionService {
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    SeckillSkuRelationService seckillSkuRelationService;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RabbitTemplate rabbitTemplate;

    // 活动缓存前缀
    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    // 商品缓存前缀
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";
    // 信号量+随机码
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<SeckillSessionEntity> page = this.page(
                new Query<SeckillSessionEntity>().getPage(params),
                new QueryWrapper<SeckillSessionEntity>()
        );

        return new PageUtils(page);
    }

    //缓存最近三天的活动
    @Override
    public void uploadSeckillSkuLatest3Days() {
        // 计算近三天的时间段（当天00:00:00-第三天23:59:59）
        LocalDate now = LocalDate.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String start = LocalDateTime.of(now, LocalTime.MIN).format(formatter);//开始的日期
        String end = LocalDateTime.of(now.plusDays(2), LocalTime.MAX).format(formatter);//加两天结束的日期

        // 获取近三天所有活动
        List<SeckillSessionEntity> sessionList = this.list(new QueryWrapper<SeckillSessionEntity>().between("start_time", start, end));
        // 没有活动返回
        if(sessionList == null && sessionList.size() == 0)    return;
        // 获取近三天所有活动关联的秒杀商品
        List<SeckillSessionEntity> collect = sessionList.stream().map(session -> {
            List<SeckillSkuRelationEntity> skuRelationList = seckillSkuRelationService.list(new QueryWrapper<SeckillSkuRelationEntity>().eq("promotion_session_id", session.getId()));
            session.setSkuRelations(skuRelationList);
            return session;
        }).collect(Collectors.toList());

        // 缓存活动信息到redis
        saveSessionInfos(collect);
        // 缓存活动的商品信息到redis
        saveSessionSkuInfos(collect);
    }
    // 缓存活动信息
    public void saveSessionInfos(List<SeckillSessionEntity> sessionList){
        sessionList.forEach(session -> {
            // key：seckill:sessions:起始日期_结束日期
            String key = SESSIONS_CACHE_PREFIX + session.getStartTime().getTime() + "_" + session.getEndTime().getTime();
            // 如果key已经存在，说明可能已经处理过，结束
            if (redisTemplate.hasKey(key))  return;

            // value：活动场次id+商品的skuId的集合
            List<String> value = session.getSkuRelations().stream()
                    .map(skuRelation -> session.getId() + "_" + skuRelation.getSkuId().toString())
                    .collect(Collectors.toList());

            // 如果活动没有商品，返回
            if(value.size() == 0)   return;

            log.info("缓存活动信息...");
            log.info(session.getId() + "活动的关联商品有" + value.toString());

            // 缓存到redis
            redisTemplate.opsForList().leftPushAll(key, value);
        });
    }
    // 缓存活动关联的商品信息
    public void saveSessionSkuInfos(List<SeckillSessionEntity> sessionList){
        sessionList.forEach(session -> {
            // 绑定到一个hash操作
            BoundHashOperations ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
            // 遍历活动关联的商品
            session.getSkuRelations().forEach(seckillSkuRelationEntity -> {
                // 创建redis传输对象，将原本的商品属性转移到更详细的redisTo
                SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
                BeanUtils.copyProperties(seckillSkuRelationEntity, redisTo);
                // 如果该hash内场次id+商品skuId的商品信息已经缓存，不执行
                if(ops.hasKey(session.getId() + "_" + redisTo.getSkuId())) return;

                // 1、获取当前商品的sku详细信息
                SkuInfoVo skuInfo = VoUtils.objectConvert(productFeignService.info(redisTo.getSkuId()).get("data"), SkuInfoVo.class);
                redisTo.setSkuInfo(skuInfo);
                // 2、把当前场次的开始和结束时间设置到当前商品
                redisTo.setStartTime(session.getStartTime().getTime());
                redisTo.setEndTime(session.getEndTime().getTime());
                // 3、给当前商品设置随机码、防止脚本提前准备开抢
                String token = UUID.randomUUID()
                        .toString().replace("-", "");
                redisTo.setRandomCode(token);

                // 4、使用库存作为分布式的信号量，限流
                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                // 5、商品可以秒杀的数量作为信号量
                semaphore.trySetPermits(redisTo.getSeckillCount().intValue());

                log.info("缓存活动关联的商品信息...");
                log.info(session.getId() + "活动的关联商品为" + redisTo.toString());

                // 保存场次id+商品skuId和对应的商品信息
                ops.put(session.getId() + "_" + redisTo.getSkuId(), redisTo);
            });
        });
    }

    //获取参加了每日秒杀的商品信息
    @Override
    public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {
        long now = System.currentTimeMillis();
        // 获取所有活动场次信息
        Set<String> keys = redisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        for(String key : keys){
            //获取场次信息的时间信息 seckill:sessions:1679097600000_1680138000000
            String[] time = key
                    .replace(SESSIONS_CACHE_PREFIX, "")
                    .split("_");
            Long startTime = Long.parseLong(time[0]);
            Long endTime = Long.parseLong(time[1]);

            // 如果当前场次的活动正在进行（默认同一时刻只有一个活动场次在进行）
            if(now >= startTime && now <= endTime){
                // 获取该场次的对应商品列表
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                System.out.println(range);
                // 绑定hash操作
                BoundHashOperations hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                // 获取所有商品对应的商品详情
                List<SecKillSkuRedisTo> redisToList = hashOps.multiGet(range);
                /*redisToList.forEach(redisTo -> {
                    // 不应该把秒杀产品的随机码发到前台，但因为获取的是已经开始的场次，应该保留
                    redisTo.setRandomCode(null);
                });*/

                return redisToList;
            }
        }
        return null;
    }

    // 获取商品的秒杀信息
    @Override
    public SecKillSkuRedisTo getSeckillSkuInfo(Long skuId) {
        // 绑定商品缓存的hash操作
        BoundHashOperations<String, String, SecKillSkuRedisTo> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        // 获取所有商品key，找到所有参与活动的商品
        Set<String> keys = hashOps.keys();
        // 通过正则匹配对应skuId的商品
        String reg = "\\d_" + skuId;
        for(String key : keys) {
            //
            if(Pattern.matches(reg, key)){
                // 匹配上了找出该商品
                SecKillSkuRedisTo secKillSkuRedisTo = hashOps.get(key);
                long now = new Date().getTime();
                // 如果活动还没开始（当前时间早于开始时间），或者活动已经结束（当前时间晚于结束时间）
                if(now < secKillSkuRedisTo.getStartTime() || now > secKillSkuRedisTo.getEndTime()){
                    // 隐藏随机码
                    secKillSkuRedisTo.setRandomCode(null);
                }
                return secKillSkuRedisTo;
            }
        }
        return null;
    }

    @Override
    public String kill(String killId, String randomKey, Integer num) {
        long seckillTime1 = System.currentTimeMillis();

        // 获取秒杀商品的详细信息
        BoundHashOperations<String, String, SecKillSkuRedisTo> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        SecKillSkuRedisTo secKillSkuRedisTo = hashOps.get(killId);
        // 为空说明该商品没有秒杀，结束
        if(secKillSkuRedisTo == null)   return null;

        // 校验秒杀时间
        long now = new Date().getTime();
        // 如果活动还没开始（当前时间早于开始时间），或者活动已经结束（当前时间晚于结束时间）
        if(now < secKillSkuRedisTo.getStartTime() || now > secKillSkuRedisTo.getEndTime()) return null;

        // 校验随机码
        if(! randomKey.equals(secKillSkuRedisTo.getRandomCode()))  return null;

        // 校验购买数量是否合理
        if(new BigDecimal(num).compareTo(secKillSkuRedisTo.getSeckillLimit()) == 1) return null;

        // 获取当前会员的id
        Long memberId = 0L;

        // 校验是否已经购买过，以设置redis占位标记是否成功作为判断：userId_SessionId_skuId
        String redisKey = memberId + "_" + killId;
        Long ttl = secKillSkuRedisTo.getEndTime() - now;
        if (! redisTemplate.opsForValue().setIfAbsent(redisKey, num, ttl, TimeUnit.MILLISECONDS)) return null;

        // 操作信号量，尝试能否按照提交的数量下单
        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomKey);
        // 信号量删减失败，返回null
        if(! semaphore.tryAcquire(num))  return null;

        // 快速下单
        String orderSn = IdWorker.getTimeId();
        SeckillOrderTo seckillOrderTo = new SeckillOrderTo()
                .setOrderSn(orderSn)
                .setPromotionSessionId(secKillSkuRedisTo.getPromotionSessionId())
                .setSkuId(secKillSkuRedisTo.getSkuInfo().getSpuId())
                .setSkuId(secKillSkuRedisTo.getSkuId())
                .setSeckillPrice(secKillSkuRedisTo.getSeckillPrice())
                .setNum(num)
                .setMemberId(memberId);   // 还没有会员id
        rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTo);

        long seckillTime2 = System.currentTimeMillis();
        log.info("秒杀耗时(ms)" + (seckillTime2 - seckillTime1));

        return orderSn;
    }
}